/*
 * psit_facade.c
 *
 *  Created on: Nov 16, 2012
 *      Author: Rui Carlos Lorenzetti da Silva
 *
 *
 */

#include <ucontext.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

//interface publicada
#include "psit_types.h"
#include "psit.h"

//estrutura de dados
#include "list.h"
#include "pqueue.h"
#include "heap.h"

#include "chtbl.h"
#include "simpletimer.h"
#include "kill_time.h"

int MAX_THREADS = 100;
int MAX_SEM = 100;
int QUANTUM_MILISECS = 500;

/**
 * Define se o sistema foi inicializado corretamente
 */
int package_init = 0;
/**
 * Define o ultimo indice da thread
 */
int thread_counter = 0;
/**
 * Define o ultimo indice do semaforo
 */
int semaphore_conter = 0;
/**
 *Hashtable para acumular os locks de cada semaforo criado.
 */
CHTbl *hashtable_sem;
/**
 * Hashtable para criar a fila de prioridade para o round robin
 */
PQueue *priority_queue;
/**
 * Contexto do shceduler;
 */
ucontext_t context_main;
/**
 * Thread corrente sendo executada
 */
thread_t *current_thread;
/**
 * Função para liberar semaforo, IE FREE
 */
void free_sem(void *sem);
/**
 * Função para liberar thread, IE FREE
 */
void free_thread(void *thread);

/**
 * Função para a troca de contexto entre threads.
 *
 */
void quantum_timer();

/**
 * Função que cria um número hash para uma chave do tipo semaforo.
 */
int hashSemaphore(const void *key);
/**
 *
 * Usado pela Hash para encontrar dentro de um bucket
 * o seu comparador.
 */
int semComparator(const void *key1, const void *key2);
/**
 * Função usada pela fila de prioridades para comparar
 * duas threads para avaliar a prioridade entre duas
 * threads quaisquer.
 *
 */
int threadComparator(const void *key1, const void *key2);

/**
 *
 */
void is_scheduler_init();

/* psit_init deve ser chamada pelo programa principal antes que qualquer
 * thread seja criada. Isso tem o efeito de criar a primeira thread, que
 * representa o programa principal, e instalar o tratador de sinais que
 * será acionado ao final de cada quantum para fazer a troca de
 * contextos.
 */
void psit_init(void) {
	if (package_init) {
		perror(
				"O sistema já foi inicializado, favor chamar scheduler_destroy.\n");
		return;
	}
	//inicializando a estrutura de dados
	hashtable_sem = malloc(sizeof(CHTbl));
	priority_queue = malloc(sizeof(Heap));
	chtbl_init(hashtable_sem, MAX_SEM, hashSemaphore, semComparator, free_sem);
	pqueue_init(priority_queue, threadComparator, free_thread);

	//inicializando o temporizador (Dispatcher quantum_timer)
	//TODO estranho aqui impressão que init_timer vai bloquear a inicialização do pacote....
	package_init = 1;
	init_timer(quantum_timer, QUANTUM_MILISECS);

}
/**
 * Função disparada a cada quantum de tempo, troca entre contextos.
 * Também conhecido como Dispatcher
 */
void quantum_timer() {

	//enquanto existir threads para rodar
	while (pqueue_size(priority_queue) > 0) {

		thread_t *thread_temp = pqueue_peek(priority_queue);
		pqueue_extract(priority_queue, current_thread);
		if (thread_temp != NULL && thread_temp->context != NULL) {
			//thread corrente
			current_thread = thread_temp;
			//troca de contexto
			current_thread->yielded = 0;
			fprintf(stderr, "Trocando o contexto para > %d ",
					current_thread->thread_id);
			if (current_thread->thread_id != -1) {
				swapcontext(&context_main, current_thread->context);
			}

		}

	}

}

/* Cada thread criada recebe um identificador único, com numeração
 * sequencial a partir de zero. Para uma thread se identificar ela deve
 * chamar a função psit_this_thread, que retorna o identificador da
 * thread corrente.
 */
int psit_this_thread(void) {
	is_scheduler_init();
	if (current_thread != NULL) {
		return current_thread->thread_id;
	}
	return -1;
}

/* Apesar do pacote de threads implementar preempção, pode ainda existir
 * o caso onde uma thread queira explicitamente abrir mão da CPU. Isso
 * é feito chamando a função psit_yield, que passará a execução para a
 * próxima thread pronta.
 */
void psit_yield(void) {
	is_scheduler_init();
	if (current_thread != NULL) {
		current_thread->yielded = 1;
		pqueue_insert(priority_queue, current_thread);
		//TODO fazer swap context de volta para main
		quantum_timer();
	} else {
		perror("Asserção incorreta no yield.\n");
	}

}

/* psit_thread_create cria a thread que executará a função tf, que será
 * iniciada com o parâmetro arg. A thread é colocada imediatamente na
 * fila de threads prontas.
 */
thread_t* psit_thread_create(thread_func tf, int arg) {
	if (thread_counter == MAX_THREADS) {
		perror("Foi alcançado o limite de threads.\n");
		return NULL;
	}

	ucontext_t *context_thread = malloc(sizeof(ucontext_t));
	thread_t *thread = malloc(sizeof(thread_t));
	if (pqueue_size(priority_queue) == 0) {
		thread->thread_id = -1;
	} else {
		thread_counter++;
		thread->thread_id = thread_counter;
	}

	//inicializando a thread
	thread->context = context_thread;
	thread->isWaiting = 0;
	thread->runnable = tf;

	//inicializando o contexto
	if (getcontext(context_thread) == -1) {
		perror("Erro ao construir o contexto para thread\n");
		return NULL;
	}
	//configurando contexto
	char *tf_stack = malloc(32384 * sizeof(char));
	(*context_thread).uc_stack.ss_sp = tf_stack;
	(*context_thread).uc_stack.ss_size = sizeof(tf_stack);
	(*context_thread).uc_link = &context_main;
	makecontext(context_thread, thread->runnable, 1, thread->thread_id);

	//configurando contexto na thread
	thread->context = context_thread;
	//adicionando na fila de prioridades
	pqueue_insert(priority_queue, thread);

	return thread;
}

/* psit_wait_threads pode ser usado pelo programa principal para esperar
 * pela execução das threads. Como qualquer programa, o retorno da função
 * main termina o processo. Sendo assim, para simplificar o código, o
 * programa principal pode criar as threads e chamar essa função para
 * ficar suspenso até que elas terminem.
 */
//TODO incompleto
void psit_wait_threads(void) {
	is_scheduler_init();
	while (pqueue_size(priority_queue) > 0) {
		kill_time();
	}

}

/* O mecanismo de exclusão mútua do pacote psit é o semáforo. O
 * funcionamento é o mesmo descrito no livro, controlado pelo seu valor
 * interno. Chamadas ao wait esperam se o valor for menor ou igual a zero.
 * Para fins de justiça, a ordem de acesso ao semáforo deve ser garantida.
 * Na criação pode-se definir o valor inicial do semáforo.
 */
sem_t* psit_sem_create(int valor) {
	is_scheduler_init();
	sem_t *semaphore_temp = malloc(sizeof(sem_t));
	semaphore_temp->sem_id = ++semaphore_conter;
	semaphore_temp->quant = valor;
	semaphore_temp->list_thread_waiting = malloc(sizeof(List));
	list_init(semaphore_temp->list_thread_waiting, free_thread);
	chtbl_insert(hashtable_sem, semaphore_temp);
	return semaphore_temp;
}
/**
 * Realiza a espera sobre o semáforo passado como parametro
 */
void psit_sem_wait(sem_t *sem) {
	is_scheduler_init();
	//se encontrar o elemento na hash
	if (!chtbl_lookup_int(hashtable_sem, sem)) {
		int quant_waiting = list_size(sem->list_thread_waiting);
		//sem espera
		if (quant_waiting == 0 && sem->quant > 0) {
			sem->quant--;
		} else if (sem->quant <= 0) {
			sem->quant--;
			list_ins_next(sem->list_thread_waiting,
					list_tail(sem->list_thread_waiting), current_thread);
			quantum_timer();
		}
	} else {
		perror("Deveria existir o semaforo na hashtable\n");
	}
}
/**
 * Libera o recurso sobre o semaforo passado como parametro
 */
void psit_sem_signal(sem_t *sem) {
	is_scheduler_init();
	thread_t *thread_removed = NULL;
	if (sem == NULL) {
		perror("Tentativa de signal nulo\n");
		return;
	}
	int quant = list_size(sem->list_thread_waiting);

	if (quant > 1) {

		ListElmt *element_temp = list_head(sem->list_thread_waiting);

		do {

			ListElmt *element_temp_next = element_temp->next;
			thread_t *thread_temp = list_data(element_temp_next);

			//teste de igualdade
			if (!threadComparator(thread_temp, current_thread)) {
				list_rem_next(sem->list_thread_waiting, element_temp,
						thread_removed);
				break;
			}
		} while ((element_temp = list_next(element_temp)) != NULL);

	} else if (quant == 1) {

		ListElmt *headElement = list_head(sem->list_thread_waiting);
		thread_t *thread_head = list_data(headElement);
		if (!threadComparator(current_thread, thread_head)) {
			list_rem_next(sem->list_thread_waiting, NULL, thread_removed);
		}
	}
	sem->quant++;

}
/**
 * Função que cria um número hash para uma chave do tipo semaforo.
 */
int hashSemaphore(const void *key) {
	if (key == NULL) {
		return 0;
	} else {
		sem_t *semaphore = (sem_t*) key;
		return semaphore->sem_id % MAX_THREADS;
	}
}
/**
 * Função usada pela fila de prioridades para comparar
 * duas threads para avaliar a prioridade entre duas
 * threads quaisquer.
 *
 * Usado pela Hashtable para encontrar dentro de um bucket
 * a thread  dentro da lista.
 *
 * retorna 1 caso key1 > key2
 * retorna -1 caso  key2 < key1
 * retorna0 caso key1 == key2
 *
 * Para isso compara:
 *
 *  1 lock de semaforo
 *  2 yield
 *  3 Chegada na lista de pronto
 *
 */
int threadComparator(const void *key1, const void *key2) {
	if (key1 == NULL || key2 == NULL) {
		perror("Valores vindos nulos na comparação das threads\n");
		return -1;
	}
	thread_t *thread1 = (thread_t*) key1;
	thread_t *thread2 = (thread_t*) key2;

	if (thread1->isWaiting & !thread2->isWaiting) {
		return 1;
	} else if (thread2->isWaiting & !thread1->isWaiting) {
		return -1;
	} else {
		if ((thread1->yielded) & !thread2->yielded) {
			return 1;
		} else if (thread2->yielded & !thread1->yielded) {
			return -1;
		} else {
			if (thread1->thread_id > thread2->thread_id) {
				return 1;
			} else if (thread2->thread_id > thread1->thread_id) {
				return -1;
			} else {
				return 0;
			}
		}
	}

}
/**
 *
 * Usado pela Hash para encontrar dentro de um bucket
 * o seu comparador.
 */
int semComparator(const void *key1, const void *key2) {
	if (key1 == NULL || key2 == NULL) {
		perror("Valores vindos nulos na comparação de semaforo\n");
		return -1;
	}
	sem_t *sem_1 = (sem_t*) key1;
	sem_t *sem_2 = (sem_t*) key2;
	if (sem_1->sem_id == sem_2->sem_id) {
		return 0;
	} else if (sem_1->sem_id > sem_2->sem_id) {
		return 1;
	} else {
		return -1;
	}
}
/**
 * Usada internamente para remover qualquer tentativa de usar operações descritas
 * pelo scheduller antes de chamar a operação.
 */
void is_scheduler_init() {
	if (!package_init) {
		perror("Operação não permitida, favor chamar init.\n");
	}
}
/**
 *Função para liberar semaforo
 */
void free_sem(void *sem) {
	if (sem == NULL) {
		return;
	}
	sem_t *sem_temp = (sem_t*) sem;
	if (sem_temp->list_thread_waiting != NULL) {
		list_destroy(sem_temp->list_thread_waiting);
	}
	free(sem_temp);
}
/**
 *Função para liberar thread
 */
void free_thread(void *thread) {
	if (thread == NULL) {
		return;
	}
	thread_t *thread_temp = (thread_t*) thread;
	if (thread_temp->context != NULL) {
		free(thread_temp->context);
	}
	if (thread_temp->stack != NULL) {
		free(thread_temp->stack);
	}
	free(thread_temp);

}
